#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

#include <iostream>

using namespace std;

#define PORT 8889

int main(int argc, char const *argv[]) {
  // 创建服务端Socket
  int s_sockfd = socket(AF_INET, SOCK_STREAM, 0);
  if (s_sockfd < 0) {
    perror("Error For socket");
    return 1;
  }
  int opt;
  // 设置端口可复用
  setsockopt(s_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
  struct sockaddr_in s_addr;
  memset(&s_addr, 0, sizeof(s_addr));
  s_addr.sin_family = AF_INET;
  s_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  s_addr.sin_port = htons(PORT);
  if (bind(s_sockfd, (struct sockaddr *)&s_addr, sizeof(s_addr)) != 0) {
    perror("Error For bind");
    return 1;
  }
  if (listen(s_sockfd, 5) < 0) {
    perror("Error For listen");
    return 1;
  }

  // 初始化select FD集合
  fd_set s_fdset;
  FD_ZERO(&s_fdset);
  // 设置FD范围区间
  int maxfd = s_sockfd + 1, minfd = s_sockfd;
  int ready = 0;
  struct sockaddr_in c_addr;
  char buff[1024]{0};
  while (1) {
    FD_SET(s_sockfd, &s_fdset);
    // 调用select后内核会对指定的描述符集合进行测试，
    // 如果发生对应事件就将该描述符对应位打开,再通过FD_ISSET（）判断对应事件是否发生
    // 参数：待检测的最大FD+1，需要监测读事件的FD集合，可写FD集合，异常FD集合，超时时间
    if ((ready = select(s_sockfd + 1, &s_fdset, NULL, NULL, NULL)) < 0) {
      perror("Error For select");
      return 1;
    }
    // 如果有新客户端连接
    if (FD_ISSET(s_sockfd, &s_fdset)) {
      socklen_t s = sizeof(c_addr);
      // 接受连接
      int c_sockfd = accept(s_sockfd, (sockaddr *)&c_addr, &s);
      if (c_sockfd < 0) {
        perror("Error For accept");
        return 1;
      }
      printf("NEW CLIENT CONNECTED [%s]\n", inet_ntoa(c_addr.sin_addr));
      // 维护FD区间
      maxfd = max(maxfd, c_sockfd) + 1;
      minfd = max(minfd, c_sockfd);
      // 将客户端socket添加到文件描述符集合
      FD_SET(c_sockfd, &s_fdset);
    }
    // 遍历文件描述符集合，检查是否有客户端socket有数据可读
    for (int i = minfd; i < maxfd; ++i) {
      if (FD_ISSET(i, &s_fdset)) {
        int n;
        if ((n = read(i, buff, sizeof(buff))) == 0) {
          close(i);
          FD_CLR(i, &s_fdset);
          printf("CLIENT DISCONNECT\n");
        } else {
          buff[n] = '\0';
          printf("RECV FROM CLIENT -> [%s]\n", buff);
        }
      }
    }
  }

  return 0;
}
